/*  tBookShelf: TextPlugin
    (C) 2007  TvN (Volodymyr Tarasenko), e-mail: tvntsr@yahoo.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


// :TODO: check exception safety


#include <iostream>
#include <libgen.h>
#include "LuaParser.hpp"

using namespace std;

class StackAutoLevel
{
    lua_State*  L;
    int         top;
public:
    StackAutoLevel(lua_State* s)
        : L(s)
    {
        top = lua_gettop(L);
    }

    ~StackAutoLevel()
    {
        if (top != 0)
            lua_settop(L, top);
    }

    void release()
    {
        top = 0;
    }
};

const char* FILE_NAME_VAR ="file_name";
const char* NAME_VAR      ="name";
const char* LINE_VAR      ="line";
const char* TRANSFORM_VAR ="transform";
const char* PROFILE_LIST_VAR="profile_list";
const char* ITEM_VAR      ="item";
const char* NEXT_VAR      ="next";
const char* FONT_VAR      ="font";
const char* STYLE_VAR     ="style";
const char* SIZE_VAR      ="size";
const char* HYPHENATION_VAR="hyphenation";
const char* BEFORE_VAR     ="before";
const char* AFTER_VAR      ="after";
const char* FIRSTLINE_VAR  ="firstline";
const char* SPACING_VAR    ="spacing";
const char* INTERLINESPACE_VAR="interlinespace";
const char* TABS_VAR       ="tabs";
const char* SCIPSPACES_VAR ="scipspaces";
const char* TEXT_STYLES_VAR="text_styles";
const char* PARAGRAPH_VAR  ="paragraph";
const char* NEW_VAR        ="new";

const char* FONT_FILE_VAR  ="font_file";
const char* USE_KERNING_VAR="use_kerning";
const char* ALIGN_VAR      ="align";

const char* LEFT           ="Left";
const char* RIGHT          ="Right";
const char* CENTER         ="Center";
const char* JUSTIFY        ="Justify";



ConfigParser::ConfigParser(const std::string& main_cfg, 
                           const char* file_name)
    : curr_profile("default")
{
    L = lua_open();               /* opens Lua */
    
    luaopen_base  (L);            /* opens the basic library */
    luaopen_table (L);            /* opens the table library */
    luaopen_string(L);            /* opens the string lib. */
    luaopen_math  (L);            /* opens the math lib. */

    lua_pushstring(L, file_name);

    lua_setglobal(L,  FILE_NAME_VAR);

    try{
        addConfig(main_cfg);

        recursiveConfig(file_name);
    }
    catch(std::exception& err)
    {
        lua_close(L);

        throw;
    }
}

ConfigParser::~ConfigParser() throw()
{
    lua_close(L);
}

void ConfigParser::addConfig(const std::string& cfg)
{
    if (luaL_loadfile(L, cfg.c_str()) || lua_pcall(L, 0, 0, 0))
    {
        luaError();
    }
}

void ConfigParser::recursiveConfig(const char* file_name)
{
    std::string yes = getStringVariable("recursive");
    std::string cfg_name = getStringVariable("recursive_file");

    if (!cfg_name.empty() && (strcasecmp(yes.c_str(), "yes") == 0))
    {
        std::list<std::string> dirs;

        char* base = strdup(file_name);

        string i;
        bool loop_quit = false;
        do
        {
            i = dirname(base);

            if (i.size()<=1)
                loop_quit = true;

            if (*(i.rbegin()) != '/')
                i += "/";
            
            i += cfg_name;

            dirs.push_back(i);
        }while(!loop_quit);

        free(base); // :FIXME: there is  memory leak in case of exception

        for(list<string>::reverse_iterator p = dirs.rbegin();
            p != dirs.rend();
            ++p)
        {
            if(0 == access(p->c_str(), R_OK))
            {
                addConfig(*p);

                yes = getStringVariable("recursive");
                if (strcasecmp(yes.c_str(), "yes") != 0)
                {
                    break;
                }
            }
        }
    }
    yes = getStringVariable("dot_configs");
    if (strcasecmp(yes.c_str(), "yes") != 0)
    {
        return;
    }

    string dot_file(file_name);
    size_t p = dot_file.rfind("/");

    if (p != string::npos)
    {
        dot_file.replace(p, 1, "/.");
    }
    else
    {
        dot_file.insert(0, ".");
    }
    dot_file += ".lua";

    if(0 == access(dot_file.c_str(), R_OK))
    {
        addConfig(dot_file);
    }
}

std::string ConfigParser::getStructName(const char* st)
{
    StackAutoLevel tr(L);
    
    // Get structure
    lua_getglobal(L, st);

    // get name field
    string name;
    if (!getLuaValue(name, NAME_VAR))
    {
        throw std::runtime_error("`object' is not a valid structure");
    }
    return name;
}

void ConfigParser::standFunction(const char* oname, 
                                 const char* fname)
{
    StackAutoLevel tr(L);

    lua_getglobal(L, oname);

    if (!lua_istable(L, -1))
    {
        throw std::runtime_error("`object' is not a valid structure");
    }

//    lua_pop(L, 1);

    lua_getfield(L, -1, fname);

    if (!lua_isfunction(L, -1))
    {
        throw std::runtime_error("`style_list' is not a valid style list");
    }

    tr.release();
}

int ConfigParser::parseLine(const std::string& line, TextStyle& style)
{
    int need_lines = 0;

    StackAutoLevel tr(L);

    standFunction(curr_profile.c_str(), LINE_VAR);

    // Pass self, arg #1
    lua_getglobal(L, curr_profile.c_str());
    
    // Pass line, arg #2
    lua_pushstring(L, line.c_str());
    
    // -3 +2, execute
    doLuaCall(2,2);

    // Result
    if (!lua_istable(L, -1))
    {
        throw std::runtime_error("It is no valid response");
    }
    if (lua_isnumber(L, -2))
    {
        need_lines = (int)lua_tonumber(L, -2);
    }

    if (need_lines == 0)
    {
        if (lua_isnil(L, -1))
        {
            throw std::runtime_error("It is no valid response");
        }
        initTextStyle(style);
    }

    return need_lines;
}

size_t ConfigParser::transformLine(std::string& line)
{
    StackAutoLevel tr(L);
    
    standFunction(curr_profile.c_str(), TRANSFORM_VAR);

    // Pass self, arg #1
    lua_getglobal(L, curr_profile.c_str());

    // Pass line, arg #2
    lua_pushstring(L, line.c_str());

    // -3 +1, execute
    doLuaCall(2, 1);

    size_t len = 0;
    line = lua_tolstring (L, -1, &len);

    return len;
}

void ConfigParser::getRegisteredStyles(std::list<std::string>& data)
{
    StackAutoLevel tr(L);

    std::string name;

    // Get structure
    lua_getglobal(L, TEXT_STYLES_VAR);

    while (!lua_isnil(L, -1))
    {
        lua_getfield(L, -1, ITEM_VAR);

        if (lua_isnil(L, -1))
        {
            lua_pop(L, 1);
            break;
        }

        if (!getLuaValue(name, NAME_VAR))
        {
            throw std::runtime_error("Bad style");
        }

        data.push_back(name);

        lua_pop(L, 1);
        
        lua_getfield(L, -1, NEXT_VAR);
        
        lua_remove(L, -2);
    }
}

void ConfigParser::getRegisteredProfiles(std::list<std::string>& data)
{
    StackAutoLevel tr(L);

    std::string name;

    // Get structure
    lua_getglobal(L, PROFILE_LIST_VAR);

    while (!lua_isnil(L, -1))
    {
        lua_getfield(L, -1, ITEM_VAR);

        if (lua_isnil(L, -1))
        {
            lua_pop(L, 1);
            break;
        }

        if (!getLuaValue(name, NAME_VAR))
        {
            throw std::runtime_error("Bad style");
        }

        data.push_back(name);

        lua_pop(L, 1);
        
        lua_getfield(L, -1, NEXT_VAR);
        
        lua_remove(L, -2);
    }
}


void ConfigParser::setProfile(const std::string& style)
{
    name_style = getStructName(style.c_str());

    curr_profile = style;
}


void
ConfigParser::initTextStyle(TextStyle& style)
{
    string nm;
    if (!getLuaValue(nm, NAME_VAR))
    {
        throw std::runtime_error("`style type' is not a valid structure");
    }
    
    if (nm == style.name)
    {
        // skip reading data, already initialed
        return;
    }

    style.name = nm;

    getLuaValue(style.font,      FONT_VAR);
    getLuaValue(style.font_file, FONT_FILE_VAR);

    getLuaValue(style.style,     STYLE_VAR);
    getLuaValue(style.size,      SIZE_VAR);
 
    getLuaValue(style.before,    BEFORE_VAR);
    getLuaValue(style.after,     AFTER_VAR );
    getLuaValue(style.firstline, FIRSTLINE_VAR);
    getLuaValue(style.spacing,   SPACING_VAR);
    getLuaValue(style.interlinespace,   INTERLINESPACE_VAR);
    getLuaValue(style.tabs,      TABS_VAR);
    getLuaValue(style.scipspaces,SCIPSPACES_VAR);
    string h;
    if(getLuaValue(h,  HYPHENATION_VAR))
    {
        style.hyphenation = strcasecmp(h.c_str(), "yes") == 0;
    }
    else
    {
        style.hyphenation = false;
    }

    if(getLuaValue(h, USE_KERNING_VAR))
    {
        style.use_kerning = strcasecmp(h.c_str(), "yes") == 0;
    }
    else
    {
        style.use_kerning = true;
    }
    
    if(getLuaValue(h, ALIGN_VAR))
    {
        if (strcasecmp(h.c_str(), LEFT) == 0)
        {
            style.align = Left;
        }
        else if (strcasecmp(h.c_str(), RIGHT) == 0)
        {
            style.align = Right;
        }
        else if (strcasecmp(h.c_str(), CENTER) == 0)
        {
            style.align = Center;
        }
        else if (strcasecmp(h.c_str(), JUSTIFY) == 0)
        {
            style.align = Justify;
        }
        else
            throw std::runtime_error("align type is not supported");
    }
    else
    {
        style.align = Justify;
    }
}

bool ConfigParser::getLuaValue(int& data, const char* name)
{
    StackAutoLevel tr(L);

    lua_getfield(L, -1, name);
    if (lua_isnil(L, -1))
    {
        return false;
    }

    // get value of field
    if (!lua_isnumber(L, -1))
    {
        throw std::runtime_error("field is not number");
    }
    data = (int)lua_tonumber(L, -1);
    
    return true;
}

bool ConfigParser::getLuaValue(unsigned int& data, const char* name)
{
    StackAutoLevel tr(L);

    lua_getfield(L, -1, name);
    if (lua_isnil(L, -1))
    {
        return false;
    }

    // get value of field
    if (!lua_isnumber(L, -1))
    {
        throw std::runtime_error("filed is not number");
    }

    data = (unsigned int)lua_tonumber(L, -1);
    
    return true;
}


bool ConfigParser::getLuaValue(string& data, const char* name)
{
    StackAutoLevel tr(L);

    lua_getfield(L, -1, name);

    if (lua_isnil(L, -1))
    {
        return false;
    }

    // get value of field
    if (!lua_isstring(L, -1))
    {
        throw std::runtime_error("field is not string");
    }

    const char *b = lua_tolstring(L, -1, 0);
    if (b)
        data = b;
    else
    {
        data = "";
    }

    return true;
}

void ConfigParser::saveTextStyle(const TextStyle& style)
{
    StackAutoLevel tr(L);

    std::string name;

    // Get structure
    lua_getglobal(L, TEXT_STYLES_VAR);
    
    while (!lua_isnil(L, -1))
    {
        lua_getfield(L, -1, ITEM_VAR);

        if (lua_isnil(L, -1))
        {
            lua_pop(L, 1);
            break;
        }

        if (!getLuaValue(name, NAME_VAR))
        {
            throw std::runtime_error("Bad style");
        }
        
        if (name == style.name)
        {
            lua_pop(L, -1); // remove name

            doSaveText(style);

            return;
        }
        
        lua_pop(L, 1);
        
        lua_getfield(L, -1, NEXT_VAR);
        
        lua_remove(L, -2);
    }

    // not found, create new one 
    createText(style);
}


void ConfigParser::doLuaCall(int nargs, int nresults, int errfunc)
{
    int err = lua_pcall(L, nargs, nresults, errfunc);
    // :TODO: get error message from stack
    switch(err)
    {
        case LUA_ERRRUN: 
            throw runtime_error("a runtime error."); 
            break;
        case LUA_ERRMEM: 
            throw runtime_error("memory allocation error."); 
            break;
        case LUA_ERRERR: 
            throw runtime_error("error while running the error handler function.");
            break;
    }

}

int ConfigParser::getIntegerVariable(const char* var_name)
{
    StackAutoLevel tr(L);

    lua_getglobal(L, var_name);

    if (lua_isnil(L, -1))
    {
        return 0;
    }

    int ret = lua_tointeger(L, -1);

    return ret;
}
std::string ConfigParser::getStringVariable(const char* var_name)
{
    StackAutoLevel tr(L);

    lua_getglobal(L, var_name);

    if (lua_isnil(L, -1))
    {
        return std::string("");
    }

    size_t len = 0;
    const char* r = lua_tolstring (L, -1, &len);

    return (r == 0)? std::string("") : std::string(r);
}


void ConfigParser::createText(const TextStyle& style)
{
    StackAutoLevel tr(L);

    standFunction(PARAGRAPH_VAR, NEW_VAR);

    // Pass self, arg #1
    lua_getglobal(L, PARAGRAPH_VAR);

    // Pass name, arg #2
    lua_pushstring(L, style.name.c_str());

    doLuaCall(2, 1);

    // Result
    if (!lua_istable(L, -1))
    {
        throw std::runtime_error("It is no valid response");
    }

    doSaveText(style);
}

void ConfigParser::doSaveText(const TextStyle& style)
{
    lua_pushstring(L, style.font.c_str());
    lua_setfield(L, -2, FONT_VAR);

    lua_pushstring(L, style.style.c_str());
    lua_setfield(L, -2, STYLE_VAR);

    lua_pushinteger(L, style.size);
    lua_setfield(L, -2, SIZE_VAR);
    
    lua_pushstring(L, style.hyphenation ? "yes" : "no");
    lua_setfield(L, -2, HYPHENATION_VAR);

    lua_pushinteger(L, style.before);
    lua_setfield(L, -2, BEFORE_VAR);
    
    lua_pushinteger(L, style.after);
    lua_setfield(L, -2, AFTER_VAR);

    lua_pushinteger(L, style.firstline);
    lua_setfield(L, -2, FIRSTLINE_VAR);

    lua_pushinteger(L, style.spacing);
    lua_setfield(L, -2, SPACING_VAR);
    
    lua_pushinteger(L, style.tabs);
    lua_setfield(L, -2, TABS_VAR);
}


void wcharToChar(char** dest, const wchar_t* data)
{
    mbstate_t ps;

    const wchar_t** src = &data;

    size_t sz = wcslen(data) * sizeof(std::wstring::value_type) + 1;

    char* tmp_name = new char[sz];

    size_t ret = wcsrtombs(tmp_name, src, sz, &ps);
    if (ret == (size_t)(-1))
    {
        perror("wcsrtombs:");

        delete[] tmp_name;
        throw runtime_error("Cannot convert wchar");
    }

    delete[] tmp_name;
}

void charToWchar(wchar_t** dest, const char* data)
{
//     mbstate_t ps;

//     const char** src = &data;

//     size_t sz = strlen(data) * sizeof(wchar_t) + sizeof(wchar_t);

//     cerr << "Try allocate " << sz << " bytes" << endl;

//     wchar_t* tmp_name = new wchar_t[sz];

//     size_t ret = wcsrtombs(tmp_name, src, sz, &ps);
//     if (ret == (size_t)(-1))
//     {
//         perror("wcsrtombs:");

//         wcerr << *src << endl;

//         delete[] tmp_name;
//         throw runtime_error("Cannot convert wchar");
//     }

//     delete[] tmp_name;
}


// TextReader::TextReader(const std::string& file)
//     : text()
// {
//     text.open(file.c_str(), ios::binary | ios::in);

//     if (!text)
//         throw runtime_error("Cannot open file ");
// }

// bool TextReader::getLine(std::string& to)
// {
//     getline(text, to);

//     return text.good();
// }
